/*-------------------------------------------------------------------------
 *
 * shell_archive.c
 *
 * 该归档函数使用用户指定的 shell 命令（archive_command GUC）来复制预写日志文件。它是默认使用的，但其他模块可以定义自己的自定义归档逻辑。
 *
 * Copyright (c) 2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/shell_archive.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/wait.h>

#include "access/xlog.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"

static bool fc_shell_archive_configured(void);
static bool fc_shell_archive_file(const char *fc_file, const char *fc_path);
static void fc_shell_archive_shutdown(void);

void shell_archive_init(ArchiveModuleCallbacks *fc_cb)
{
	AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);

	fc_cb->check_configured_cb = fc_shell_archive_configured;
	fc_cb->archive_file_cb = fc_shell_archive_file;
	fc_cb->shutdown_cb = fc_shell_archive_shutdown;
}

static bool fc_shell_archive_configured(void)
{
	return XLogArchiveCommand[0] != '\0';
}

static bool fc_shell_archive_file(const char *fc_file, const char *fc_path)
{
	char		fc_xlogarchcmd[MAXPGPATH];
	char	   *fc_dp;
	char	   *fc_endp;
	const char *fc_sp;
	int			fc_rc;

	/*
	 * 构造要执行的命令
	 */
	fc_dp = fc_xlogarchcmd;
	fc_endp = fc_xlogarchcmd + MAXPGPATH - 1;
	*fc_endp = '\0';

	for (fc_sp = XLogArchiveCommand; *fc_sp; fc_sp++)
	{
		if (*fc_sp == '%')
		{
			switch (fc_sp[1])
			{
				case 'p':
					/* %p: 源文件的相对路径 */
					fc_sp++;
					strlcpy(fc_dp, fc_path, fc_endp - fc_dp);
					make_native_path(fc_dp);
					fc_dp += strlen(fc_dp);
					break;
				case 'f':
					/* %f: 源文件的文件名 */
					fc_sp++;
					strlcpy(fc_dp, fc_file, fc_endp - fc_dp);
					fc_dp += strlen(fc_dp);
					break;
				case '%':
					/* 将%%转换为一个% */
					fc_sp++;
					if (fc_dp < fc_endp)
						*fc_dp++ = *fc_sp;
					break;
				default:
					/* 否则将%视为普通字符 */
					if (fc_dp < fc_endp)
						*fc_dp++ = *fc_sp;
					break;
			}
		}
		else
		{
			if (fc_dp < fc_endp)
				*fc_dp++ = *fc_sp;
		}
	}
	*fc_dp = '\0';

	ereport(DEBUG3,
			(errmsg_internal("executing archive command \"%s\"",
							 fc_xlogarchcmd)));

	pgstat_report_wait_start(WAIT_EVENT_ARCHIVE_COMMAND);
	fc_rc = system(fc_xlogarchcmd);
	pgstat_report_wait_end();

	if (fc_rc != 0)
	{
		/*
		 * 如果 shell 本身或被调用的命令因信号而终止，
		 * 则中止归档程序。我们这样做是因为 system() 在等待时会忽略 SIGINT 和
		 * SIGQUIT；因此，信号很可能是应该中断我们的事情。另外，如果 shell 收到了一个硬
		 * “命令未找到”类型的错误，也会导致终止。如果我们反应过度也没关系,
		 * 主进程会自动重新启动归档程序。
		 */
		int			fc_lev = wait_result_is_any_signal(fc_rc, true) ? FATAL : LOG;

		if (WIFEXITED(fc_rc))
		{
			ereport(fc_lev,
					(errmsg("archive command failed with exit code %d",
							WEXITSTATUS(fc_rc)),
					 errdetail("The failed archive command was: %s",
							   fc_xlogarchcmd)));
		}
		else if (WIFSIGNALED(fc_rc))
		{
#if defined(WIN32)
			ereport(lev,
					(errmsg("archive command was terminated by exception 0x%X",
							WTERMSIG(rc)),
					 errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."),
					 errdetail("The failed archive command was: %s",
							   xlogarchcmd)));
#else
			ereport(fc_lev,
					(errmsg("archive command was terminated by signal %d: %s",
							WTERMSIG(fc_rc), pg_strsignal(WTERMSIG(fc_rc))),
					 errdetail("The failed archive command was: %s",
							   fc_xlogarchcmd)));
#endif
		}
		else
		{
			ereport(fc_lev,
					(errmsg("archive command exited with unrecognized status %d",
							fc_rc),
					 errdetail("The failed archive command was: %s",
							   fc_xlogarchcmd)));
		}

		return false;
	}

	elog(DEBUG1, "archived write-ahead log file \"%s\"", fc_file);
	return true;
}

static void fc_shell_archive_shutdown(void)
{
	elog(DEBUG1, "archiver process shutting down");
}
